package com.fs.luban.log.aspect;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Enumeration;
import java.util.Map;
import java.util.TreeMap;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.fs.luban.log.annotation.Olog;
import com.fs.luban.log.service.LogWork;
import com.fs.luban.module.sys.entity.SysOlog;
import com.fs.luban.util.Reflections;
import com.fs.luban.util.RequestUtils;
import com.fs.luban.util.Servlets;
import com.google.common.collect.Maps;

import eu.bitwalker.useragentutils.Browser;
import eu.bitwalker.useragentutils.OperatingSystem;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.extern.log4j.Log4j2;

/**
 * @title 操作日志切面
 * @Description 操作日志切面
 * @author fengshi
 */
@Log4j2
@Aspect
@Component
public class OlogAspect {

	@Autowired
	private LogWork logWork;
	@Autowired
	private LogAdviceExpand logAdviceExpand;

	private String ignoreParameters = "ec*,autoInc,submit";
	private String ignoreAttribute = "springframework,encodingFilter";

	@Pointcut("execution(public * com.fs.luban.module..*.controller..*.*(..))")
	public void ologAspectPointcut() {

	}

	/**
	 * 日志处理切面
	 * 
	 * @param pjp
	 * @return
	 * @throws Throwable
	 */
	@Around("ologAspectPointcut()")
	public Object around(ProceedingJoinPoint pjp) throws Throwable {
		Object result = null;
		// 计算处理时间
		StopWatch clock = StopWatch.createStarted();
		try {
			result = pjp.proceed();
		} catch (Exception e) {
			result = e;
			throw e;
		} finally {
			clock.stop(); // 计时结束
			long executeMilliseconds = clock.getTime();
			try {
				log(pjp, result, executeMilliseconds);
			} catch (Exception ex) {
				log.error("luban log err:{}", ex);
			}
		}
		return result;
	}

	public void log(ProceedingJoinPoint pjp, Object result, long executeMilliseconds) {
		Object target = pjp.getTarget();
		String methodName = pjp.getSignature().getName();
		String className = pjp.getTarget().getClass().getSimpleName();
		// 获取日志注解
		Method method = Reflections.getAccessibleMethodByName(target, methodName);
		Olog annotLog = method.getAnnotation(Olog.class);
		// 日志打印
		log.debug("execTime:{}ms;({}.{})", executeMilliseconds, target.getClass().getName(), methodName);
		if (log.isTraceEnabled()) {
			// 获取request
			HttpServletRequest request = getRequest();
			if (request != null) {
				// 客户端信息
				String clientInformations = getClientInformations(request);
				// request参数
				Map<String, Object> parmMap = Servlets.getParametersStartingWith(request, null);
				// request属性
				Enumeration<String> attrNames = request.getAttributeNames();
				Map<String, Object> attrMap = Maps.newTreeMap();
				while (attrNames != null && attrNames.hasMoreElements()) {
					String arrtName = (String) attrNames.nextElement();
					if (isIgnoreAttribute(arrtName) == false) {
						attrMap.put(arrtName, request.getAttribute(arrtName));
					}
				}
				// springMap
				Map<?, ?> springMap = getSpringMap(pjp.getArgs());
				log.trace(
						"execTime:{}ms; url:{}; clientInfo:{}; requestParam:{}; requestAttribute:{}; springMap:{}; ({}.{})",
						executeMilliseconds, request.getRequestURI(), clientInformations, parmMap.toString(),
						attrMap.toString(), springMap.toString(), target.getClass().getName(), methodName);
			}
		}
		// 如果没有Log注解，不记录日志
		if (annotLog == null) {
			return;
		}
		// 收集日志信息，并持久化，如果出错忽略。
		SysOlog olog = new SysOlog();
		olog.setExecuteMilliseconds(executeMilliseconds);
		olog.setName(annotLog.name());
		olog.setRemark(annotLog.remark());
		olog.setClassName(className);
		olog.setClassMethed(methodName);
		olog.setOperateTime(Calendar.getInstance().getTime());
		// 判断结果是否正常
		if (result instanceof Exception) {
			olog.setOperateResult(0);
			olog.setOperateMessage(result.getClass().getName());
		} else {
			olog.setOperateResult(1);
		}
		try {
			HttpServletRequest request = getRequest();
			olog.setClientInfo(getClientInformations(request));
			olog.setRequestUri(request.getRequestURI());
			olog.setRequestMethod(request.getMethod());
			handleParameters(request, target, methodName, pjp.getArgs(), olog, annotLog);
			if (logAdviceExpand != null) {
				logAdviceExpand.expand(request, olog);
			}
		} catch (Exception e) {
			log.error("olog request error:", e);
		}
		log.debug(olog.toString());
		// 持久化日志
		logWork.doWork(olog);
	}

	protected void handleParameters(HttpServletRequest request, Object target, String methodName, Object args[],
			SysOlog olog, Olog annotLog) {
		if (!annotLog.needRecordParameter()) {
			return;
		}
		String requestParameters = Arrays.toString(args);
		if (request != null) {
			requestParameters = getRequestParameters(request, target, methodName, annotLog, args);
		}
		requestParameters = StringUtils.substring(requestParameters, 0, 256);
		olog.setRequestParameters(requestParameters);
	}

	private String getRequestParameters(HttpServletRequest request, Object target, String methodName, Olog annotLog,
			Object[] args) {
		String parameters = "";
		if (annotLog.needRecordParameter()) {
			Map<String, Object> requestParameters = Servlets.getParametersStartingWith(request, null);
			// 解析@PathVariable注解的参数
			try {
				Annotation[][] annotations = Reflections.getAccessibleMethodByName(target, methodName)
						.getParameterAnnotations();
				for (int j = 0; j < annotations.length; j++) {
					Annotation[] annots = annotations[j];
					for (Annotation annot : annots) {
						if (annot instanceof PathVariable) {
							PathVariable var = (PathVariable) annot;
							requestParameters.put(var.value(), args[j]);
						}
					}
				}
			} catch (Exception ex) {
				log.error("annotations param error:", ex);
			}
			Map<String, String> paramNameMapping = getParameterMapping(target, methodName, annotLog);

			String ignorParams = "";

			if (annotLog.ignorParam() != null) {
				String ignors[] = annotLog.ignorParam();
				if (ignors != null && ignors.length > 0) {
					ignorParams = "," + StringUtils.join(ignors, ",") + ",";
				}
			}
			Map<String, Object> confirmParameters = new TreeMap<String, Object>();
			for (Map.Entry<String, Object> entry : requestParameters.entrySet()) {
				if (!isIgnoreParameter(entry.getKey(), annotLog)
						&& !StringUtils.contains(ignorParams, "," + entry.getKey() + ",") && entry.getValue() != null
						&& StringUtils.isNotBlank(entry.getValue().toString())) {
					String key = entry.getKey();
					if (StringUtils.isNotBlank(paramNameMapping.get(key))) {
						key = paramNameMapping.get(key);
					}
					confirmParameters.put(key, entry.getValue());
				}
			}
			if (confirmParameters.size() > 0) {
				parameters = confirmParameters.toString();
			}
		}
		return parameters;
	}

	private Map<String, String> getParameterMapping(Object target, String methodName, Olog annotLog) {
		Map<String, String> paramMapping = Maps.newHashMap();
		if (annotLog.paramMapping() != null) {
			String[] mapping = annotLog.paramMapping();
			if (mapping != null && mapping.length > 0) {
				for (String e : mapping) {
					String[] entry = StringUtils.split(e, ":");
					if (entry != null && entry.length == 2) {
						paramMapping.put(entry[0], entry[1]);
					}
				}
			}
		}
		return paramMapping;
	}

	/**
	 * 过滤掉request attribute 的参数
	 * 
	 * @param attribureName
	 * @return
	 */
	private boolean isIgnoreAttribute(String attribureName) {
		if (StringUtils.isBlank(ignoreAttribute) || StringUtils.isBlank(attribureName)) {
			return true;
		}
		String[] ignores = StringUtils.split(ignoreAttribute, ",");
		for (String str : ignores) {
			if (attribureName.indexOf(str) != -1) {
				return true;
			}
		}
		return false;
	}

	/**
	 * 判断是否忽略的参数
	 *
	 * @param parameterName
	 * @return
	 */
	private boolean isIgnoreParameter(String parameterName, Olog annotLog) {
		String[] ignores = StringUtils.split(ignoreParameters, ",");
		for (String ignoreStr : ignores) {
			if (isLike(ignoreStr, parameterName)) {
				return true;
			}
		}
		return false;
	}

	private boolean isLike(String srcIncludeStar, String dest) {
		if ("*".equals(srcIncludeStar)) {
			return true;
		} else if (srcIncludeStar.indexOf("*") == 0) {
			if (dest.indexOf(srcIncludeStar.substring(1, srcIncludeStar.length())) == dest.length()
					- srcIncludeStar.length() + 1) {
				return true;
			} else {
				return false;
			}
		} else if (srcIncludeStar.indexOf("*") == srcIncludeStar.length() - 1) {
			if (dest.indexOf(srcIncludeStar.substring(0, srcIncludeStar.length() - 1)) == 0) {
				return true;
			} else {
				return false;
			}
		} else if (srcIncludeStar.equalsIgnoreCase(dest)) {
			return true;
		}
		return false;
	}

	/**
	 * 获取请求客户端信息
	 * 
	 * @param request
	 * @return
	 */
	private String getClientInformations(HttpServletRequest request) {
		if (request == null)
			return "";
		String clientIP = RequestUtils.getRequestIpAddr(request);
		String requestUserAgent = request.getHeader("User-Agent");
		UserAgent userAgent = UserAgent.parseUserAgentString(requestUserAgent);
		OperatingSystem os = userAgent.getOperatingSystem();
		Browser browser = userAgent.getBrowser();
		StringBuffer sb = new StringBuffer();
		sb.append(clientIP).append("-")
			.append(os.getName()).append("-")
			.append(browser.getName())
			.append("/").append(browser.getBrowserType().getName());
		return sb.toString();
	}

	/**
	 * 获取Controller 的 map参数
	 * 
	 * @param args
	 * @return
	 */
	private Map<?, ?> getSpringMap(Object[] args) {
		Map<?, ?> map = Maps.newHashMap();
		for (Object arg : args) {
			if (arg instanceof Map) {
				map = (Map<?, ?>) arg;
				break;
			}
		}
		return map;
	}

	/**
	 * 获取Controller 的 HttpServletRequest
	 * 
	 * @param args
	 * @return
	 */
	private HttpServletRequest getRequest() {
		RequestAttributes ra = RequestContextHolder.getRequestAttributes();
		ServletRequestAttributes sra = (ServletRequestAttributes) ra;
		return sra.getRequest();
	}

}
